package org.manon.json.asm;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.manon.json.JSONException;
import org.manon.json.util.ConvertUtil;
import org.manon.json.util.NumberUtil;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

final class ASMConverterHolder {

	private final Map<Class<?>,ASMConverter> _converters;
	private Map<Class<?>,Class<?>> _primitiveMap = new HashMap<Class<?>,Class<?>>();
	
	public ASMConverterHolder() {
		_converters = new HashMap<Class<?>, ASMConverter>();
		addPrimitiveTypes();
		addClassPrimitiveTypes();
		addObjectTypes();
		_primitiveMap.put(Integer.TYPE, Integer.class);
		_primitiveMap.put(Long.TYPE, Long.class);
		_primitiveMap.put(Double.TYPE, Double.class);
		_primitiveMap.put(Float.TYPE, Float.class);
		_primitiveMap.put(Boolean.TYPE, Boolean.class);
		_primitiveMap.put(Character.TYPE, Character.class);
		_primitiveMap.put(Byte.TYPE, Byte.class);
		_primitiveMap.put(Void.TYPE, Void.class);
		_primitiveMap.put(Short.TYPE, Short.class);
	}
	
	
	ASMConverter getConverter(Class<?> clazz) {
		
		final ASMConverter converter =_converters.get(clazz);
		
		if (converter == null) {
			throw new JSONException("No ASMConverter for Class [" + clazz + "]");
		}
		
		return converter;
	}
	
	private void addPrimitiveTypes() {
		_converters.put(Boolean.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Boolean.class),"valueOf","(Ljava/lang/String;)Ljava/lang/Boolean;"); 
				methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Boolean.class),"booleanValue","()Z"); 
			}
		});
		_converters.put(Byte.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitVarInsn(Opcodes.BIPUSH, 10);
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Byte.class),"parseByte",
						Type.getMethodDescriptor(Type.getType(byte.class), new Type[]{Type.getType(String.class),Type.getType(int.class)})); 
			}
		});
		_converters.put(Short.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitVarInsn(Opcodes.BIPUSH, 10);
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Short.class),"parseShort",
						Type.getMethodDescriptor(Type.getType(short.class), new Type[]{Type.getType(String.class),Type.getType(int.class)})); 
			}
		});
		_converters.put(Integer.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(NumberUtil.class),"parseInt",
						Type.getMethodDescriptor(Type.getType(int.class), new Type[]{Type.getType(String.class)})); 
			}
		});
		_converters.put(Long.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(NumberUtil.class),"parseLong",
						Type.getMethodDescriptor(Type.getType(long.class), new Type[]{Type.getType(String.class)})); 
			}
		});
		_converters.put(Float.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Float.class),"parseFloat",
						Type.getMethodDescriptor(Type.getType(float.class), new Type[]{Type.getType(String.class)})); 
			}
		});
		_converters.put(Double.TYPE, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Double.class),"parseDouble",
						Type.getMethodDescriptor(Type.getType(double.class), new Type[]{Type.getType(String.class)})); 
			}
		});
	}
	
	private void addClassPrimitiveTypes() {
		_converters.put(Boolean.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Boolean.class),"valueOf","(Ljava/lang/String;)Ljava/lang/Boolean;"); 
			}
		});
		_converters.put(Byte.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitVarInsn(Opcodes.BIPUSH, 10);
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Byte.class),"valueOf",
						Type.getMethodDescriptor(Type.getType(Byte.class), new Type[]{Type.getType(String.class),Type.getType(int.class)}));
			}
		});
		_converters.put(Short.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitVarInsn(Opcodes.BIPUSH, 10);
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Short.class),"valueOf",
						Type.getMethodDescriptor(Type.getType(Short.class), new Type[]{Type.getType(String.class),Type.getType(int.class)}));
			}
		});
		_converters.put(Integer.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(NumberUtil.class),"parseInteger",
						Type.getMethodDescriptor(Type.getType(Integer.class), new Type[]{Type.getType(String.class)}));
			}
		});
		_converters.put(Long.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(NumberUtil.class),"parseLongOb",
						Type.getMethodDescriptor(Type.getType(Long.class), new Type[]{Type.getType(String.class)}));
			}
		});
		_converters.put(Float.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Float.class),"valueOf",
						Type.getMethodDescriptor(Type.getType(Float.class), new Type[]{Type.getType(String.class)}));
			}
		});
		_converters.put(Double.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Double.class),"valueOf",
						Type.getMethodDescriptor(Type.getType(Double.class), new Type[]{Type.getType(String.class)}));
			}
		});
		
	}
	
	
	private void addObjectTypes() {
		_converters.put(Calendar.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ConvertUtil.class),"parseCalendar",
						Type.getMethodDescriptor(Type.getType(Calendar.class), new Type[]{Type.getType(String.class)}));
			}
		});
		
		_converters.put(Date.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ConvertUtil.class),"parseDate",
						Type.getMethodDescriptor(Type.getType(Date.class), new Type[]{Type.getType(String.class)}));
			}
		});
		
		_converters.put(Timestamp.class, new ASMConverter() {
			public void convert(MethodVisitor methodVisitor) {
				methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(ConvertUtil.class),"parseTimeStamp",
						Type.getMethodDescriptor(Type.getType(Timestamp.class), new Type[]{Type.getType(String.class)}));
			}
		});
	}
	
	public static void convertEnum(MethodVisitor methodVisitor, Class<?> enumClass) {
		methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(enumClass),"valueOf",
				Type.getMethodDescriptor(Type.getType(enumClass), new Type[]{Type.getType(String.class)}));
	}
	
}